Skip to main content
Version: V5

V4 to V5

Standalone Components Migration

  1. Open a terminal into your angular root for each of your angular front project
  2. Run command ng g @angular/core:standalone
    1. Select Convert standalone
    2. Choose current root path of your project and validate
  3. Run command ng g @angular/core:standalone again
    1. Select Remove modules
    2. Choose current root path of your project and validate
  4. Run npm run clean command
  5. Commit the changes

Angular 19 Migration

Editor's migration guides

Angular CDK (v16 -> v17)

  1. Open a terminal into your angular root project
  2. Run command ng update @angular/cdk@17, you'll have the following automatic migrations :
    1. From @angular/cdk
      1. Updates the Angular CDK to v17
  3. Commit the changes

Angular Core (v17 -> v18)

  1. Run command ng update @angular/core@18 @angular/cli@18 @angular-eslint/schematics@18, you'll have the following automatic migrations :
    1. From @angular-eslint/schematics
      1. Updates @angular-eslint to v18.2
    2. From @angular/cli
      1. Migrate application projects to the new build system : you'll have to accept the migration action use-application-builder
    3. From @angular/core
      1. Updates two-way bindings that have an invalid expression to use the longform expression instead.
      2. Replace deprecated HTTP related modules with provider functions
      3. Updates calls to afterRender with an explicit phase to the new API
  2. Commit the changes

Angular Packages (v17 -> v18)

  1. Run command ng update @angular/cdk@18 @ngrx/store@18 @ngrx/store-devtools@18 keycloak-angular@16 primeng@18, you'll have the following automatic migrations :
    1. From @angular/cdk
      1. Updates the Angular CDK to v18
    2. From @ngrx/effects
      1. As of NgRx v18, the concatLatestFrom import has been removed from @ngrx/effects in favor of the @ngrx/operators package
    3. From @ngrx/store
      1. As of NgRx v18, the TypedAction has been removed in favor of Action
  2. Commit the changes

Angular Core + Packages (v18 -> v19)

  1. Run command ng update @angular/core@19 @angular/cli@19 @angular/build@19 @angular/animations@19 @angular-eslint/schematics@19 @angular/cdk@19 @ngrx/store@19 @ngrx/store-devtools@19 keycloak-angular@19 primeng@19, you'll have the following automatic migrations :
    1. From @angular/cli
      1. Update '@angular/ssr' import paths to use the new '/node' entry point when 'CommonEngine' is detected
      2. Update the workspace configuration by replacing deprecated options in 'angular.json' for compatibility with the latest Angular CLI changes
      3. Migrate application projects to the new build system : you'll have to ignore the migration action use-application-builder
    2. From @angular/cdk
      1. Updates the Angular CDK to v19
    3. From @angular/core
      1. Updates non-standalone Directives, Component and Pipes to 'standalone:false' and removes 'standalone:true' from those who are standalone
      2. Updates ExperimentalPendingTasks to PendingTasks
      3. Replaces APP_INITIALIZER, ENVIRONMENT_INITIALIZER & PLATFORM_INITIALIZER respectively with provideAppInitializer, provideEnvironmentInitializer & providePlatformInitializer : you'll have to accept the migration action provide-initialize
    4. From @ngrx/effects
      1. As of NgRx v18, the concatLatestFrom import has been removed from @ngrx/effects in favor of the @ngrx/operators package
    5. From @ngrx/store
      1. As of NgRx v18, the TypedAction has been removed in favor of Action
  2. Commit the changes

BIA Framework Migration

  1. Delete from your Angular projects all package-lock.json and node_modules folder
  2. Use the BIAToolKit to migrate the project :
    • Run it automatically by clicking on Migrate button
      or
    • Execute each step manually until step 3 - Apply Diff
  3. Mind to check the output logs to check any errors or missing deleted files
  4. Manage the conflicts (two solutions) :
    • Merging rejected files
      • Execute step 4 - Merge Rejected (already executed with automatic migration)
      • Search <<<<< in all files
      • Resolve the conflicts
    • Analyzing rejected files - MANUAL MIGRATION ONLY
      • Analyze all the .rej files (search "diff a/" in VS code)
      • Apply manually the changes into your files
    tip

    Use the conflict resolution chapter to help you

  5. For each Angular project, launch the npm install and npm audit fix command
  6. Download the migration script and the standalone catch up script (right click -> Save link as) into the same directory
    1. Rename the downloaded scripts to remove the hash of the name (remove all after and including the last -)
    2. Change source path of the migration script to target your project root and your Angular project
    3. Run it for each of your Angular project (change the Angular source path each time)
  7. Apply other manual steps for Front (for each Angular project) and Back
  8. Resolve missing and obsolete usings in back-end with BIAToolKit (step 6 - Resolve Usings)
  9. Resolve building issues into your Angular projects and back end
  10. If all is ok, you can remove the .rej files. During the process they can be useful to resolve build problems
  11. Execute the database migration instructions
  12. For each Angular project, launch the npm run clean command
  13. Clean back-end solution
  14. Customize your application logo (doc)

Conflict resolution

You must pay attention to the conflict resoltuon of the following files.

TranslationModelBuilder.cs

  • Call the base method into CreateModel, CreateLanguageModel, CreateRoleTranslationModel, CreateNotificationTypeTranslationModel
  • Keep only one instruction of each HasData method

You must have this kind of result at the end :

TranslationModelBuilder.cs
/// <summary>
/// Class used to update the model builder for notification domain.
/// </summary>
public class TranslationModelBuilder : BaseTranslationModelBuilder
{
/// <summary>
/// Create the user model.
/// </summary>
/// <param name="modelBuilder">The model builder.</param>
public override void CreateModel(ModelBuilder modelBuilder)
{
base.CreateModel(modelBuilder);
// Add here the project specific translation model creation.
Debug.Assert(modelBuilder != null, "Line to avoid warning empty method");
}

/// <summary>
/// Create the model for notification.
/// </summary>
/// <param name="modelBuilder">The model builder.</param>
protected override void CreateLanguageModel(ModelBuilder modelBuilder)
{
base.CreateLanguageModel(modelBuilder);

modelBuilder.Entity<Language>().HasData(new Language { Id = LanguageId.English, Code = "EN", Name = "English" });
modelBuilder.Entity<Language>().HasData(new Language { Id = LanguageId.French, Code = "FR", Name = "Français" });
modelBuilder.Entity<Language>().HasData(new Language { Id = LanguageId.Spanish, Code = "ES", Name = "Española" });

// Add here your own Languages
}

/// <summary>
/// Create the model for notification.
/// </summary>
/// <param name="modelBuilder">The model builder.</param>
protected override void CreateRoleTranslationModel(ModelBuilder modelBuilder)
{
base.CreateRoleTranslationModel(modelBuilder);

modelBuilder.Entity<RoleTranslation>().HasData(new RoleTranslation { RoleId = (int)BiaRoleId.Admin, LanguageId = LanguageId.French, Id = 1000101, Label = "Administrateur" });
modelBuilder.Entity<RoleTranslation>().HasData(new RoleTranslation { RoleId = (int)BiaRoleId.Admin, LanguageId = LanguageId.Spanish, Id = 1000102, Label = "Administrador" });
modelBuilder.Entity<RoleTranslation>().HasData(new RoleTranslation { RoleId = (int)BiaRoleId.BackAdmin, LanguageId = LanguageId.French, Id = 1000201, Label = "Administrateur des tâches en arrière-plan" });
modelBuilder.Entity<RoleTranslation>().HasData(new RoleTranslation { RoleId = (int)BiaRoleId.BackAdmin, LanguageId = LanguageId.Spanish, Id = 1000202, Label = "Administrador de tareas en segundo plano" });
modelBuilder.Entity<RoleTranslation>().HasData(new RoleTranslation { RoleId = (int)BiaRoleId.BackReadOnly, LanguageId = LanguageId.French, Id = 1000301, Label = "Visualisation des tâches en arrière-plan" });
modelBuilder.Entity<RoleTranslation>().HasData(new RoleTranslation { RoleId = (int)BiaRoleId.BackReadOnly, LanguageId = LanguageId.Spanish, Id = 1000302, Label = "Visualización de tareas en segundo plano" });

// Add here your own RoleTranslations
}

/// <summary>
/// Create the model for notification.
/// </summary>
/// <param name="modelBuilder">The model builder.</param>
protected override void CreateNotificationTypeTranslationModel(ModelBuilder modelBuilder)
{
base.CreateNotificationTypeTranslationModel(modelBuilder);

modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = (int)BiaNotificationTypeId.Task, LanguageId = LanguageId.French, Id = 101, Label = "Tâche" });
modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = (int)BiaNotificationTypeId.Task, LanguageId = LanguageId.Spanish, Id = 102, Label = "Tarea" });
modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = (int)BiaNotificationTypeId.Info, LanguageId = LanguageId.French, Id = 201, Label = "Information" });
modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = (int)BiaNotificationTypeId.Info, LanguageId = LanguageId.Spanish, Id = 202, Label = "Información" });
modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = (int)BiaNotificationTypeId.Success, LanguageId = LanguageId.French, Id = 301, Label = "Succès" });
modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = (int)BiaNotificationTypeId.Success, LanguageId = LanguageId.Spanish, Id = 302, Label = "Éxito" });
modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = (int)BiaNotificationTypeId.Warning, LanguageId = LanguageId.French, Id = 401, Label = "Avertissement" });
modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = (int)BiaNotificationTypeId.Warning, LanguageId = LanguageId.Spanish, Id = 402, Label = "Advertencia" });
modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = (int)BiaNotificationTypeId.Error, LanguageId = LanguageId.French, Id = 501, Label = "Erreur" });
modelBuilder.Entity<NotificationTypeTranslation>().HasData(new NotificationTypeTranslation { NotificationTypeId = (int)BiaNotificationTypeId.Error, LanguageId = LanguageId.Spanish, Id = 502, Label = "Culpa" });

// Add here your own NotificationTypeTranslations
}
}

UserModelBuilder.cs

  • Call the base method into CreateUserModel, CreateTeamTypeModel, CreateRoleModel, CreateTeamTypeRoleModel
  • Keep only one instruction of each HasData method

You must have this kind of result at the end :

UserModelBuilder.cs
/// <summary>
/// Class used to update the model builder for user domain.
/// </summary>
public class UserModelBuilder : BaseUserModelBuilder
{
/// <summary>
/// Create the model for users.
/// </summary>
/// <param name="modelBuilder">The model builder.</param>
protected override void CreateUserModel(ModelBuilder modelBuilder)
{
base.CreateUserModel(modelBuilder);
modelBuilder.Entity<User>(entity =>
{
entity.Property(u => u.Email).HasMaxLength(256);
});
}

/// <summary>
/// Create the model for teams.
/// </summary>
/// <param name="modelBuilder">The model builder.</param>
protected override void CreateTeamTypeModel(ModelBuilder modelBuilder)
{
base.CreateTeamTypeModel(modelBuilder);

modelBuilder.Entity<TeamType>().HasData(new TeamType { Id = (int)TeamTypeId.Site, Name = "Site" });

// BIAToolKit - Begin TeamTypeModelBuilder
// BIAToolKit - End TeamTypeModelBuilder
}

/// <summary>
/// Create the model for roles.
/// </summary>
/// <param name="modelBuilder">The model builder.</param>
protected override void CreateRoleModel(ModelBuilder modelBuilder)
{
base.CreateRoleModel(modelBuilder);
modelBuilder.Entity<Role>().HasData(new Role { Id = (int)BiaRoleId.Admin, Code = "Admin", Label = "Administrator" });
modelBuilder.Entity<Role>().HasData(new Role { Id = (int)BiaRoleId.BackAdmin, Code = "Back_Admin", Label = "Background task administrator" });
modelBuilder.Entity<Role>().HasData(new Role { Id = (int)BiaRoleId.BackReadOnly, Code = "Back_Read_Only", Label = "Visualization of background tasks" });
// Add here your own Roles

// BIAToolKit - Begin RoleModelBuilder
// BIAToolKit - End RoleModelBuilder
}

/// <summary>
/// Create the model for member roles.
/// </summary>
/// <param name="modelBuilder">The model builder.</param>
protected override void CreateTeamTypeRoleModel(ModelBuilder modelBuilder)
{
base.CreateTeamTypeRoleModel(modelBuilder);

modelBuilder.Entity<Role>()
.HasMany(p => p.TeamTypes)
.WithMany(r => r.Roles)
.UsingEntity(rt =>
{
rt.ToTable("RoleTeamTypes");
rt.HasData(new { TeamTypesId = (int)BiaTeamTypeId.Root, RolesId = (int)BiaRoleId.Admin });
rt.HasData(new { TeamTypesId = (int)BiaTeamTypeId.Root, RolesId = (int)BiaRoleId.BackAdmin });
rt.HasData(new { TeamTypesId = (int)BiaTeamTypeId.Root, RolesId = (int)BiaRoleId.BackReadOnly });
// Add here your own RoleTeamTypes

// BIAToolKit - Begin TeamTypeRoleModelBuilder
// BIAToolKit - End TeamTypeRoleModelBuilder
});
}
}

Front Manual Steps

  1. Replace HttpClientModule import by provideHttpClient(withInterceptorsFromDi()) into the providers :
Before
import { HttpClientModule } from '@angular/common/http';

@NgModule({
imports: [
HttpClientModule
]
})
After
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';

@NgModule({
providers: [
provideHttpClient(withInterceptorsFromDi())
]
})
  1. Replace all @Output properties with native event names such as cancel, click by another names (ex: cancelled, clicked)
  2. Remove Time usage from your code
  3. Adapt the usage of <p-tabs> migrated from <p-tabView> :
    1. Add <p-tablist> inside and under <p-tabs>
    2. For each existing <p-tabpanel>, add a <p-tab> inside <p-tablist> with an incremental numeric property value, with the inner content of previous header property of the <p-tabpanel> or of the <ng-template pTemplate="header"> content
    3. Replace for each existing <p-tabpanel> property header by value with corresponding value of the added <p-tab>
    4. Add into the <p-tabs> a property value with the same identifier as your target tab to set as active by default
Before
<p-tabView>
<p-tabPanel *ngFor="let tab of tabs" header="tab.title">
{{ tab.content }}
</p-tabPanel>
</p-tabView>

<p-tabView>
<p-tabPanel *ngFor="let tab of tabs">
<ng-template pTemplate="header">
{{ tab.title }}
</ng-template>
{{ tab.content }}
</p-tabPanel>
</p-tabView>
After
<p-tabs [value]="0">
<p-tablist>
@for (tab of tabs; track $index) {
<p-tab [value]="$index">
{{ tab.title }}
</p-tab>
}
</p-tablist>
@for (tab of tabs; track $index) {
<p-tabpanel [value]="$index">
{{ tab.content }}
</p-tabpanel>
}
</p-tabs>
tip

Check the Tabs PrimeNG documentation to adapt the input/output bindings

  1. Fix the automatic replacements of <p-floatlabel> to the correct HTML tag if any
  2. Fix the automatic replacements of <p-fluid> to the correct HTML tag if any
  3. For each <p-checkbox> with label, add into div parent container the classes flex items-center
Before
<div>
<p-checkbox></p-checkbox>
<label>Something</label>
</div>
After
<div class="flex items-center">
<p-checkbox></p-checkbox>
<label>Something</label>
</div>
  1. The eslint plugin eqeqeq (forcing usage of === instead of ==) should be activated after migration. You can keep it activated and fix all lint errors or you can deactivate it in the .eslintrc file of your project by changing every line where eqeqeq appears by :
"eqeqeq": "off"
  1. Moving advanced filters to the right :
    1. Move advanced filter html after table in index.component
    2. Move from table-header to table-controller html :
[showBtnFilter]="true"
[showFilter]="showAdvancedFilter"
[hasFilter]="hasAdvancedFilter"
(openFilter)="onOpenFilter()"
  1. Replace into your .scss files the usage of @import by @use and add the import file name as suffix of the concerned properties
Before
@import '../theme'

button {
color: $button-color /* From theme */
}
After
@use '../theme'

button {
color: theme.$button-color
}
  1. It is no longer needed to use local assets as backgrounds for ColorPicker of PrimeNG
  2. Change references of AuthInfo.additionalInfo.userInfo.id to AuthInfo.decryptedToken.id (If you were using const additionalInfo = this.authService.getAdditionalInfos(); to retrieve this value, use instead const decryptedToken = this.authService.getDecryptedToken())
  3. Change references of AuthInfo.additionalInfo.userInfo.login to AuthInfo.decryptedToken.identityKey (Same as above, use instead const decryptedToken = this.authService.getDecryptedToken())
  4. Replace in HTML templates of components that inherits from CrudItemFormComponent the reference of output cancel by cancelled
  5. <p-badge [value]="property"> : property must not be undefined or null
  6. If previously using <p-card> with a <p-header> and/or <p-footer>, change theses tags by <ng-template #header> or <ng-template #footer>. You can then delete the import Header and/or Footer from your TS component.
Before
<p-card>
<p-header></p-header>
<p-footer></p-footer>
</p-card>
After
<p-card>
<ng-template #header></ng-template>
<ng-template #footer></ng-template>
</p-card>
  1. Class .p-fluid has disappeared from primeng. Search for all references to class p-fluid in your html files and use instead the new primeng html element <p-fluid>. Adapt your style : for example, if replaced html element was a div, the default display: block from the div might be lost when changing it to p-fluid html element.
  2. Class .p-float-label has disappeared from primeng. If you have custom css using these classes, you will have to replace it for it to work again. Search in all your css files references to classes .p-float-label and fix the style in found pages.
  3. The constructor of BiaTableControllerComponent has changed. A new parameter Renderer2 has been added. If you have a component inheriting from BiaTableControllerComponent, you need to add it as shown in this example:
export class CustomTableControllerComponent extends BiaTableControllerComponent {
constructor(
public translateService: TranslateService,
protected renderer: Renderer2
) {
super(translateService, renderer);
}
}
  1. Remove into all your feature's modules the imports of your view components
Before
@NgModule({
imports: [
RouterModule.forChild(ROUTES),
StoreModule.forFeature(
myFeatureCRUDConfiguration.storeKey,
FeatureMyFeaturesStore.reducers
),
EffectsModule.forFeature([MyFeaturesEffects]),
SomeOptionModule,
// Your previous views imports
MyFeatureItemComponent,
MyFeaturesIndexComponent,
MyFeatureFormComponent,
MyFeatureNewComponent,
MyFeatureEditComponent,
MyFeatureTableComponent,
MyFeatureImportComponent,
],
})
export class MyFeatureModule {}
After
@NgModule({
imports: [
RouterModule.forChild(ROUTES),
StoreModule.forFeature(
myFeatureCRUDConfiguration.storeKey,
FeatureMyFeaturesStore.reducers
),
EffectsModule.forFeature([MyFeaturesEffects]),
SomeOptionModule,
],
})
export class MyFeatureModule {}

Back Manual Steps

Entities

  1. Into your Mappers, be careful on DtoToEntity function : the initialization of entity is mandatory else you will have crash during the creation of an item:
    1. Search public override void DtoToEntity
    2. Verify that there is one of these lines in the first row of the function:
      1. base.DtoToEntity(dto, ref entity);
      2. entity ??= new ...
      3. entity = new TEntity ...
    3. If not add base.DtoToEntity(dto, ref entity); at the beginning.
  2. Build the solution, search and resolve all CS0108 warnings from your entities : remove all properties that hides inherited properties from BaseEntityXXX or BaseDtoXXX classes

User

  1. Remove from Infrastructure.Data.ModelBuilders.UserModelBuilder class into CreateUserModel method all entities configuration that refers to properties included already into BaseEntityUser
  2. Changes declarations of UserOptionMapper by providing a generic BaseEntityUser type. Example : UserOptionMapper<User>
  3. If you have any custom method into UserSpecification used, handle reference to your project instead of BIA.Net.Core
  4. Apply changes into Domain.User.Mappers.UserMapper
    1. Into ExpressionCollection, modify the HeaderName struct reference to HeaderNameExtended for all of your properties and remove all mapping for properties included into HeaderName struct
    2. Remove from EntityToDto the mapping of properties already included into BaseEntityUser class
    3. Do the same for DtoToEntity
    4. Rewrite into DtoToCellMapping the previous content of the method DtoToRecord for each properties of your custom User class
Before
public override Func<UserDto, object[]> DtoToRecord(List<string> headerNames = null)
{
return x =>
{
List<object> records = new List<object>();

if (headerNames?.Any() == true)
{
foreach (string headerName in headerNames)
{
if (string.Equals(headerName, HeaderName.MyProperty, StringComparison.OrdinalIgnoreCase))
{
records.Add(CSVString(x.MyProperty));
}

// ...
}
}

return records.ToArray();
};
}
After
public override Dictionary<string, Func<string>> DtoToCellMapping(UserDto dto)
{
return new Dictionary<string, Func<string>>(base.DtoToCellMapping(dto))
{
{ HeaderNameExtended.MyProperty, () => CSVString(dto.MyProperty) },
// ...
};
}

Database Migration

  1. Add a new migration MigrationBiaFrameworkV5
  2. Edit the generated migration :
    1. Search for the following code into Up()
    migrationBuilder.DropColumn(
    name: "IsDefault",
    table: "Members");
    1. Add before the following code :
    migrationBuilder.Sql(@"
    INSERT INTO UserDefaultTeam (UserId, TeamId)
    SELECT UserId, TeamId
    FROM Members
    WHERE IsDefault = 1;
    ");
    warning

    Ensure to have this previous code after the CreateTable and CreateIndexinstruction for UserDefaultTeam table

    1. Search for the following code into Up()
    migrationBuilder.AddColumn<string>(
    name: "Discriminator",
    table: "Users",
    type: "nvarchar(21)",
    maxLength: 21,
    nullable: false,
    defaultValue: "");
    1. Change the defaultValue to User
    2. Search for the following code into Down()
    migrationBuilder.AddColumn<bool>(
    name: "IsDefault",
    table: "Members",
    type: "bit",
    nullable: false,
    defaultValue: false);
    1. Add after the following code :
    migrationBuilder.Sql(@"
    INSERT INTO Members (UserId, TeamId, IsDefault)
    SELECT UserId, TeamId, 1
    FROM UserDefaultTeam;
    ");
    warning

    Ensure to have this previous code before the DropTable instruction for UserDefaultTeam table

  3. Update database